#include "Timer.hpp"
#include "Clock.hpp"
#include "AssetManager.hpp"
#include "Notification.hpp"
#include "Util.hpp"
#include "States/DefaultState.hpp"
#include "States/DialogState.hpp"
#include <cpp3ds/System/I18n.hpp>
#include <cpp3ds/System/FileSystem.hpp>
#include <TweenEngine/Tween.h>
#include <time.h>
#include <stdlib.h>
#include <cmath>
#ifndef EMULATION
#include "KeyboardApplet.hpp"
#endif

namespace Clock {

Timer::Timer()
: m_stopButtonInteract(false)
, m_timeHour(0)
, m_timeMinute(0)
, m_timeSecond(0)
, m_requestAddMinute(false)
{
	m_textTimerMinutes.setString("00  ");
	m_textTimerMinutes.setCharacterSize(32);
	m_textTimerMinutes.useSystemFont();
	m_textTimerMinutes.setFillColor(cpp3ds::Color::Black);
	m_textTimerMinutes.setOrigin(m_textTimerMinutes.getLocalBounds().width / 2, m_textTimerMinutes.getLocalBounds().height / 2);
	m_textTimerMinutes.setPosition(160.f, 100.f);

	m_textTimerHours = m_textTimerMinutes;
	m_textTimerHours.setOrigin(m_textTimerHours.getLocalBounds().width, m_textTimerHours.getLocalBounds().height / 2);
	m_textTimerHours.setPosition(m_textTimerMinutes.getPosition().x - (m_textTimerMinutes.getLocalBounds().width / 2) - 5.f, m_textTimerMinutes.getPosition().y);

	m_textTimerSeconds = m_textTimerMinutes;
	m_textTimerSeconds.setOrigin(0.f, m_textTimerSeconds.getLocalBounds().height / 2);
	m_textTimerSeconds.setPosition(m_textTimerMinutes.getPosition().x + (m_textTimerMinutes.getLocalBounds().width / 2) + 5.f, m_textTimerMinutes.getPosition().y);
	m_textTimerSeconds.setString("00");
	m_textTimerSeconds.setFillColor(g_themeColor);

	m_startButton.setRadius(20.f);
	m_startButton.setPosition(30.f, 210.f);
	m_startButton.setFillColor(cpp3ds::Color(0, 150, 136));
	m_startButton.setOutlineThickness(1);
	m_startButton.setOutlineColor(cpp3ds::Color(0, 105, 92));
	m_startButton.setOrigin(m_startButton.getLocalBounds().width / 2, m_startButton.getLocalBounds().height / 2);

	m_textStart.setPosition(m_startButton.getPosition().x, m_startButton.getPosition().y);
	m_textStart.setString("\uf04b");
	m_textStart.setFillColor(cpp3ds::Color::White);
	m_textStart.setOrigin(m_textStart.getLocalBounds().width / 2, m_textStart.getLocalBounds().height / 2);
	m_textStart.setCharacterSize(16);
	m_textStart.setOutlineThickness(1);
	m_textStart.setOutlineColor(cpp3ds::Color::Black);
	m_textStart.setFont(AssetManager<cpp3ds::Font>::get("fonts/fontawesome.ttf"));

	m_stopButton = m_startButton;
	m_stopButton.setPosition(290.f, 210.f);
	m_stopButton.setFillColor(cpp3ds::Color(244, 67, 54, 0));
	m_stopButton.setOutlineColor(cpp3ds::Color(198, 40, 40, 0));

	m_textStop = m_textStart;
	m_textStop.setString("\uf04d");
	m_textStop.setOrigin(m_textStop.getLocalBounds().width / 2, m_textStop.getLocalBounds().height / 1.6f);
	m_textStop.setPosition(m_stopButton.getPosition().x, m_stopButton.getPosition().y);
	m_textStop.setFillColor(cpp3ds::Color(255, 255, 255, 0));
	m_textStop.setOutlineColor(cpp3ds::Color(0, 0, 0, 0));

	m_textAddMinute = m_textStart;
	m_textAddMinute.setString("\uf067");
	m_textAddMinute.setOrigin(m_textAddMinute.getLocalBounds().width / 2, m_textAddMinute.getLocalBounds().height / 2);
	m_textAddMinute.setPosition(120.f, 210.f);

	m_textResetTimer = m_textStart;
	m_textResetTimer.setString("\uf00d");
	m_textResetTimer.setOrigin(m_textResetTimer.getLocalBounds().width / 2, m_textResetTimer.getLocalBounds().height / 2);
	m_textResetTimer.setPosition(180.f, 210.f);

	m_textHourTwoPoints.setFillColor(cpp3ds::Color(66, 66, 66));
	m_textHourTwoPoints.useSystemFont();
	m_textHourTwoPoints.setString(":");
	m_textHourTwoPoints.setPosition(m_textTimerHours.getPosition().x - m_textTimerHours.getLocalBounds().width / 8, m_textTimerHours.getPosition().y);
	m_textHourTwoPoints.setCharacterSize(24);
	m_textHourTwoPoints.setOrigin(m_textHourTwoPoints.getLocalBounds().width / 2, m_textHourTwoPoints.getLocalBounds().height / 2);

	m_textMinuteTwoPoints = m_textHourTwoPoints;
	m_textMinuteTwoPoints.setPosition(m_textTimerMinutes.getPosition().x + m_textTimerMinutes.getLocalBounds().width / 4 + 7.f, m_textTimerMinutes.getPosition().y);

	m_soundTimerOver.setBuffer(AssetManager<cpp3ds::SoundBuffer>::get("sounds/bip.ogg"));
	m_soundTimerOver.setLoop(true);
}

Timer::~Timer()
{

}

void Timer::draw(cpp3ds::RenderTarget &target, cpp3ds::RenderStates states) const
{
	states.transform *= getTransform();

	target.draw(m_textTimerHours, states);
	target.draw(m_textTimerMinutes, states);
	target.draw(m_textTimerSeconds, states);

	target.draw(m_textHourTwoPoints, states);
	target.draw(m_textMinuteTwoPoints, states);

	target.draw(m_startButton, states);
	target.draw(m_textStart, states);

	if (m_mainStopwatch.getElapsedTime() > cpp3ds::Time::Zero)
		target.draw(m_textAddMinute, states);

	if (m_mainStopwatch.getElapsedTime() <= cpp3ds::Time::Zero && m_timeLeft > cpp3ds::Time::Zero)
		target.draw(m_textResetTimer, states);

	if (m_stopButtonInteract) {
		target.draw(m_stopButton, states);
		target.draw(m_textStop, states);
	}
}

void Timer::update(float delta)
{
	m_tweenManager.update(delta);

	cpp3ds::Time timeRemaining = getRemainingTime();
	if (isRunning() && timeRemaining > cpp3ds::Time::Zero) {
		int seconds = (int)ceil(timeRemaining.asSeconds()) % 60;
		int minutes = (int)ceil(timeRemaining.asSeconds()) / 60;
		if (m_requestAddMinute) {
			m_requestAddMinute = false;
			m_timeLeft += cpp3ds::seconds(60.f);
			minutes++;
		}
		int hours = minutes / 60;
		minutes %= 60;

		m_timeHour = hours;
		m_timeMinute = minutes;
		m_timeSecond = seconds;

		m_textTimerHours.setString(_("%02d  ", hours));
		m_textTimerMinutes.setString(_("%02d  ", minutes));
		m_textTimerSeconds.setString(_("%02d", seconds));
	} else if (timeRemaining <= cpp3ds::Time::Zero && isRunning()) {
		stop();
		m_soundTimerOver.play();

		g_defaultState->requestStackPush(States::Dialog, false, [=](void *data) mutable {
			auto event = reinterpret_cast<DialogState::Event*>(data);
			if (event->type == DialogState::GetText)
			{
				auto str = reinterpret_cast<cpp3ds::String*>(event->data);
				*str = _("The timer is over !");
				return true;
			}
			else if (event->type == DialogState::GetTitle) {
				auto str = reinterpret_cast<cpp3ds::String *>(event->data);
				*str = _("Timer");
				return true;
			}
			else if (event->type == DialogState::ShowCancelButton) {
				auto bol = reinterpret_cast<bool *>(event->data);
				*bol = false;
				return true;
			}
			else if (event->type == DialogState::Response)
			{
				bool *accepted = reinterpret_cast<bool*>(event->data);
				if (*accepted)
				{
					m_soundTimerOver.stop();
				}
				return true;
			}
			return false;
		});
	}
}

void Timer::processEvent(const cpp3ds::Event& event)
{
	if (event.type == cpp3ds::Event::TouchEnded) {
		if (m_startButton.getGlobalBounds().contains(event.touch.x, event.touch.y)) {
			toggle();
		} else if (m_stopButton.getGlobalBounds().contains(event.touch.x, event.touch.y) && m_stopButtonInteract) {
			stop();
		} else if (m_textTimerHours.getGlobalBounds().contains(event.touch.x, event.touch.y)) {
			updateUnit(&m_timeHour);
		} else if (m_textTimerMinutes.getGlobalBounds().contains(event.touch.x, event.touch.y)) {
			updateUnit(&m_timeMinute);
		} else if (m_textTimerSeconds.getGlobalBounds().contains(event.touch.x, event.touch.y)) {
			updateUnit(&m_timeSecond);
		} else if (m_textResetTimer.getGlobalBounds().contains(event.touch.x, event.touch.y)) {
			resetNumbers();
		} else if (m_textAddMinute.getGlobalBounds().contains(event.touch.x, event.touch.y)) {
			addMinute();
		}
	} else if (event.type == cpp3ds::Event::KeyPressed) {
		switch (event.key.code) {
			case cpp3ds::Keyboard::A:
				toggle();
				break;
			case cpp3ds::Keyboard::X:
				addMinute();
				break;
			case cpp3ds::Keyboard::Y:
				resetNumbers();
				break;
		}
	}
}

void Timer::play()
{
	if (m_timeLeft <= cpp3ds::Time::Zero)
		return;

	m_mainStopwatch.resume();

	m_textStart.setString("\uf04c");
	m_textStart.setOrigin(m_textStart.getLocalBounds().width / 2, m_textStart.getLocalBounds().height / 1.6f);
	TweenEngine::Tween::to(m_startButton, m_startButton.FILL_COLOR_RGB, 0.4f)
		.target(255, 235, 59)
		.start(m_tweenManager);

	TweenEngine::Tween::to(m_startButton, m_startButton.OUTLINE_COLOR_RGB, 0.4f)
		.target(249, 168, 37)
		.start(m_tweenManager);

	m_stopButtonInteract = true;
	TweenEngine::Tween::to(m_stopButton, m_stopButton.FILL_COLOR_ALPHA, 0.4f)
		.target(255)
		.start(m_tweenManager);
	TweenEngine::Tween::to(m_stopButton, m_stopButton.OUTLINE_COLOR_ALPHA, 0.4f)
		.target(255)
		.start(m_tweenManager);

	TweenEngine::Tween::to(m_textStop, m_textStop.FILL_COLOR_ALPHA, 0.4f)
		.target(255)
		.start(m_tweenManager);
	TweenEngine::Tween::to(m_textStop, m_textStop.OUTLINE_COLOR_ALPHA, 0.4f)
		.target(255)
		.start(m_tweenManager);
}

void Timer::pause()
{
	m_mainStopwatch.pause();

	m_textStart.setString("\uf04b");
	m_textStart.setOrigin(m_textStart.getLocalBounds().width / 2, m_textStart.getLocalBounds().height / 1.6f);
	TweenEngine::Tween::to(m_startButton, m_startButton.FILL_COLOR_RGB, 0.4f)
		.target(0, 150, 136)
		.start(m_tweenManager);

	TweenEngine::Tween::to(m_startButton, m_startButton.OUTLINE_COLOR_RGB, 0.4f)
		.target(0, 105, 92)
		.start(m_tweenManager);
}

void Timer::stop()
{
	pause();
	reset();

	TweenEngine::Tween::to(m_stopButton, m_stopButton.FILL_COLOR_ALPHA, 0.4f)
		.target(0)
		.setCallback(TweenEngine::TweenCallback::COMPLETE, [=](TweenEngine::BaseTween* source) {
			m_stopButtonInteract = false;
		})
		.start(m_tweenManager);
	TweenEngine::Tween::to(m_stopButton, m_stopButton.OUTLINE_COLOR_ALPHA, 0.4f)
		.target(0)
		.start(m_tweenManager);

	TweenEngine::Tween::to(m_textStop, m_textStop.FILL_COLOR_ALPHA, 0.4f)
		.target(0)
		.start(m_tweenManager);
	TweenEngine::Tween::to(m_textStop, m_textStop.OUTLINE_COLOR_ALPHA, 0.4f)
		.target(0)
		.start(m_tweenManager);
}

void Timer::toggle()
{
	if (!isRunning())
		play();
	else
		pause();
}

bool Timer::isRunning()
{
	return m_mainStopwatch.isRunning();
}

cpp3ds::Time Timer::getElapsedTime()
{
	return m_mainStopwatch.getElapsedTime();
}

void Timer::reset()
{
	m_mainStopwatch.reset();

	m_timeSecond = (int)ceil(m_timeLeft.asSeconds()) % 60;
	m_timeMinute = (int)ceil(m_timeLeft.asSeconds()) / 60;
	m_timeHour = m_timeMinute / 60;
	m_timeMinute %= 60;

	updateTimeTexts();
}

void Timer::updateTimeTexts()
{
	if (m_timeSecond >= 60) {
		m_timeSecond %= 60;
		m_timeMinute++;
	}

	if (m_timeMinute >= 60) {
		m_timeMinute %= 60;
		m_timeHour++;
	}

	m_timeLeft = cpp3ds::seconds(m_timeHour * 3600 + m_timeMinute * 60 + m_timeSecond);

	int seconds = (int)ceil(m_timeLeft.asSeconds()) % 60;
	int minutes = (int)ceil(m_timeLeft.asSeconds()) / 60;
	int hours = minutes / 60;
	minutes %= 60;

	m_textTimerHours.setString(_("%02d  ", hours));
	m_textTimerMinutes.setString(_("%02d  ", minutes));
	m_textTimerSeconds.setString(_("%02d", seconds));

	if (m_mainStopwatch.getElapsedTime() > cpp3ds::Time::Zero) {
		m_mainStopwatch.reset();
		m_mainStopwatch.add(cpp3ds::microseconds(1));
	}
}

int Timer::getNumberFromUser(int defaultNumber)
{
#ifndef EMULATION
	KeyboardApplet kb(KeyboardApplet::Number, 2);
	if (defaultNumber > 0)
		swkbdSetInitialText(kb, std::to_string(defaultNumber).c_str());
	swkbdSetFeatures(kb, SWKBD_FIXED_WIDTH);
	cpp3ds::String input = kb.getInput();
	if (!input.isEmpty())
		return stoi(input.toAnsiString());
#else
	return 5;
#endif

	return -1;
}

void Timer::updateUnit(int *unit)
{
	if (isRunning())
		return;

	int returnedNumber = getNumberFromUser(*unit);
	if (returnedNumber >= 0)
		*unit = returnedNumber;

	updateTimeTexts();
}

cpp3ds::Time Timer::getRemainingTime()
{
	return m_timeLeft - m_mainStopwatch.getElapsedTime();
}

void Timer::resetNumbers()
{
	if (m_mainStopwatch.getElapsedTime() > cpp3ds::Time::Zero)
		return;

	m_timeHour = 0;
	m_timeMinute = 0;
	m_timeSecond = 0;

	updateTimeTexts();
}

void Timer::addMinute()
{
	if (m_mainStopwatch.getElapsedTime() <= cpp3ds::Time::Zero)
		return;

	if (isRunning()) {
		m_requestAddMinute = true;
	} else {
		m_timeMinute++;
		m_timeLeft += cpp3ds::seconds(60.f);

		m_textTimerMinutes.setString(_("%02d  ", m_timeMinute));
	}
}

} // namespace Clock
